package de.hska.master.algo.gui;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Stack;

import javax.swing.JFrame;

import de.hska.master.algo.QuickSort;

public class Viewer extends JFrame {

	public class MyMouseMotionListener extends MouseMotionAdapter {

		private Viewer v;

		public MyMouseMotionListener(Viewer v) {
			this.v = v;
		}

		@Override
		public void mouseMoved(MouseEvent arg0) {
			Point p = arg0.getPoint();
			v.updateMousePosition(p);
			v.repaint();
		}
	}

	public class MyMouseListener extends MouseAdapter {

		private Viewer v;

		public MyMouseListener(Viewer v) {
			this.v = v;
		}

		@Override
		public void mouseClicked(MouseEvent arg0) {
			Point p = arg0.getPoint();
			System.out.println("v.addPoint(new Point(" + p.x + ", " + p.y
					+ "));");
			v.addPoint(p);
		}
	}

	private ArrayList<Point> points = new ArrayList<Point>();
	private ArrayList<Point> convexHull = new ArrayList<Point>();

	private Point lastMousePosition = null;

	private long timeOfLastRepaint = 0;

	private void init() {
		timeOfLastRepaint = System.currentTimeMillis();
	}

	public Viewer() {
		init();
		this.addMouseListener(new MyMouseListener(this));
		this.addMouseMotionListener(new MyMouseMotionListener(this));
	}

	public Viewer(ArrayList<Point> points) {
		init();
		this.points = points;
	}

	public void addPoint(Point point) {
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		points.add(point);
	}

	public void addPoint(int x, int y) throws InterruptedException {
		Thread.sleep(500);

		points.add(new Point(x, y));
	}

	public void addPoints(ArrayList<Point> points) {
		this.points.addAll(points);
	}

	public void addPoints(Point[] points) {
		this.points.addAll(Arrays.asList(points));
	}

	public void addPointOnConvexHull(Point point) throws InterruptedException {
		convexHull.add(point);
		Thread.sleep(500);
	}

	public void addPointOnConvexHull(int x, int y) throws InterruptedException {
		Thread.sleep(500);

		convexHull.add(new Point(x, y));
	}

	public void removePoint(Point pointToRemove) {
		points.remove(pointToRemove);
		convexHull.remove(pointToRemove);
	}

	public void removePointOnConvexHull(Point pointToRemove)
			throws InterruptedException {
		Thread.sleep(500);
		convexHull.remove(pointToRemove);
	}

	public void paint(Graphics g) {
		if (System.currentTimeMillis() - timeOfLastRepaint < 30) {
			return;
		}
		timeOfLastRepaint = System.currentTimeMillis();

		super.paint(g);

		drawConvexHull(g);
		drawPoints(g);
		drawMousePosition(g);
	}

	final int widthArcAroundPoint = 20;
	final int heightArcAroundPoint = 20;

	final int widthFilledMiniArcAroundPoint = widthArcAroundPoint / 2;
	final int heightFilledMiniArcAroundPoint = heightArcAroundPoint / 2;

	private void drawPoints(Graphics g) {
		for (Point point : points) {
			int x = point.x - widthArcAroundPoint / 2;
			int y = point.y - heightArcAroundPoint / 2;
			g.drawArc(x, y, widthArcAroundPoint, heightArcAroundPoint, 0, 360);

			if (point == closestPoint) {
				g.fillArc(x, y, widthArcAroundPoint, heightArcAroundPoint, 0,
						360);
			}

			x = point.x - widthFilledMiniArcAroundPoint / 2;
			y = point.y - heightFilledMiniArcAroundPoint / 2;

			g.fillArc(x, y, widthFilledMiniArcAroundPoint,
					heightFilledMiniArcAroundPoint, 0, 360);

		}
	}

	private void drawConvexHull(Graphics g) {
		Point[] pointsToPaint = new Point[convexHull.size()];
		convexHull.toArray(pointsToPaint);

		final int nPoints = pointsToPaint.length;
		int[] xPoints = new int[nPoints];
		int[] yPoints = new int[nPoints];

		for (int i = 0; i < nPoints; ++i) {
			int xComponent = pointsToPaint[i].x;
			int yComponent = pointsToPaint[i].y;
			xPoints[i] = xComponent;
			yPoints[i] = yComponent;
		}

		g.drawPolygon(xPoints, yPoints, nPoints);
	}

	private void drawMousePosition(Graphics g) {
		if (lastMousePosition == null)
			return;

		int mousePosX = lastMousePosition.x;
		int mousePosY = lastMousePosition.y;

		g.drawString("[x=" + mousePosX + "|y=" + mousePosY + "]", mousePosX,
				mousePosY);
	}

	public void updateMousePosition(Point newMousePosition) {
		lastMousePosition = newMousePosition;

		moveCursorIfCloseToPoint();
	}

	final int PROXIMITY_DIST = 30;
	private Point closestPoint = null;

	private void moveCursorIfCloseToPoint() {
		Point closestPoint = null;

		for (Point point : points) {
			double currentMinDistance = lastMousePosition.distance(point);
			if (currentMinDistance < PROXIMITY_DIST) {
				closestPoint = point;// new min dist point
			}
		}

		if (this.closestPoint != closestPoint) {
			this.closestPoint = closestPoint;
		}
	}

	public static int getKreuzprodukt(Point a, Point p0, Point p1) {
		int isClockwise = (a.x - p0.x) * (p1.y - p0.y) - (p1.x - p0.x)
				* (a.y - p0.y);
		return isClockwise;
	}

	public static void main(String[] args) throws InterruptedException {
		Viewer viewer = new Viewer();

		viewer.setVisible(true);
		viewer.setSize(1024, 960);
		viewer.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		viewer.setLocationRelativeTo(null);

		Point point0 = new Point(0, 200);
		Point point1 = new Point(100, 200);
		Point point2 = new Point(200, 200);
		Point point3 = new Point(200, 100);
		Point point4 = new Point(200, 300);
		Point point5 = new Point(300, 200);
		Point point6 = new Point(500, 200);
		Point reference = new Point(100, 100);

		Point[] points = new Point[7];
		points[0] = point4;
		points[1] = point2;
		points[2] = point3;
		points[3] = point0;
		points[4] = point1;
		points[5] = point6;
		points[6] = point5;

		viewer.addPoints(points);
		viewer.addPoint(reference.x, reference.y);

		QuickSort sort = new QuickSort(points, reference);
		sort.sort();
		Point[] sortedPoints = sort.getPoints();

		Stack<Point> stack = new Stack<Point>();
		stack.push(reference);
		stack.push(sortedPoints[0]);
		stack.push(sortedPoints[1]);
		viewer.addPointOnConvexHull(reference.x, reference.y);
		viewer.addPointOnConvexHull(sortedPoints[0].x, sortedPoints[0].y);
		viewer.addPointOnConvexHull(sortedPoints[1].x, sortedPoints[1].y);

		for (int i = 2; i < sortedPoints.length; i++) {
			while (Viewer.getKreuzprodukt(stack.peek(),
					stack.get(stack.size() - 2), sortedPoints[i]) < 0) {
				viewer.removePointOnConvexHull(new Point(
						stack.get(stack.size() - 1).x,
						stack.get(stack.size() - 1).y));
				stack.pop();
			}
			stack.push(sortedPoints[i]);
			viewer.addPointOnConvexHull(sortedPoints[i].x, sortedPoints[i].y);
		}

		// for (int i = 0; i < stack.size(); i++) {
		// viewer.addPointOnConvexHull(stack.get(i).x, stack.get(i)
		// .y);
		// }

		// Point noConvexHullPoint1 = new Point(514, 363);// convex hull
		// viewer.addPointOnConvexHull(noConvexHullPoint1);// must be removed
		// later
		// // -> not in convex hull
		//
		// Point noConvexHullPoint2 = new Point(323, 351);
		// viewer.addPointOnConvexHull(noConvexHullPoint2);// must be removed
		// later
		// // -> not in convex hull
		//
		// viewer.addPointOnConvexHull(168, 492);// part of convex hull
		// viewer.addPointOnConvexHull(209, 179);// part of convex hull
		// viewer.addPointOnConvexHull(615, 181);// part of convex hull
		// viewer.addPointOnConvexHull(639, 497);// part of convex hull
		//
		// Thread.sleep(2000);
		//
		// viewer.removePointOnConvexHull(noConvexHullPoint1);
		// viewer.removePointOnConvexHull(noConvexHullPoint2);
		//
		// Thread.sleep(2000);
		//
		// viewer.removePoint(new Point(168, 492));
	}
}